package com.ccp.dev.workflow.job;

import com.ccp.dev.core.basic.api.ISysUser;
import com.ccp.dev.core.basic.consts.SystemConst;
import com.ccp.dev.core.basic.engine.GroovyScriptEngine;
import com.ccp.dev.core.basic.scheduler.BaseJob;
import com.ccp.dev.core.basic.util.ContextUtil;
import com.ccp.dev.core.basic.util.PropertyUtil;
import com.ccp.dev.core.basic.util.UUIDUtils;
import com.ccp.dev.core.model.MessageModel;
import com.ccp.dev.core.util.AppUtil;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.core.util.TimeUtil;
import com.ccp.dev.system.dao.SysUserDao;
import com.ccp.dev.system.model.MessageSend;
import com.ccp.dev.system.model.SysUser;
import com.ccp.dev.workflow.bpmconst.BpmConst;
import com.ccp.dev.workflow.dao.ReminderStateDao;
import com.ccp.dev.workflow.dao.TaskDao;
import com.ccp.dev.workflow.dao.TaskReminderDao;
import com.ccp.dev.workflow.model.ProcessRun;
import com.ccp.dev.workflow.model.ReminderState;
import com.ccp.dev.workflow.model.TaskReminder;
import com.ccp.dev.workflow.model.TaskWarningSet;
import com.ccp.dev.workflow.model.bpm.ProcessCmd;
import com.ccp.dev.workflow.model.bpm.ProcessTask;
import com.ccp.dev.workflow.service.BpmService;
import com.ccp.dev.workflow.service.ProcessRunService;
import com.ccp.dev.workflow.service.TaskUserService;
import com.ccp.dev.workflow.service.jms.MessageProducer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.RuntimeServiceImpl;
import org.flowable.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.quartz.JobExecutionContext;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * ClassDescribe:催办任务
 *
 * @author :jiayueqiao
 * Date: 2019-10-24
 * Since:1
 */
public class ReminderJob extends BaseJob {

    /**
     * 流程提醒
     */
    private static final int REMIND = 1;
    /**
     * 执行到期处理动作
     */
    private static final int COMPLETE = 2;
    private Log logger = LogFactory.getLog(ReminderJob.class);
    @Override
    public void executeJob(JobExecutionContext context) throws Exception {
        TaskReminderDao taskReminderDao = (TaskReminderDao) AppUtil.getBean(TaskReminderDao.class);
        ReminderStateDao reminderStateDao = (ReminderStateDao) AppUtil.getBean(ReminderStateDao.class);
        GroovyScriptEngine groovyScriptEngine = (GroovyScriptEngine) AppUtil.getBean(GroovyScriptEngine.class);
        RuntimeService runtimeService = (RuntimeServiceImpl) AppUtil.getBean(RuntimeServiceImpl.class);
        //清除过期的任务状态数据。
        reminderStateDao.delExpiredTaskReminderState();
        TaskDao taskDao = (TaskDao) AppUtil.getBean(TaskDao.class);
        // 获取未到期的任务
        List<ProcessTask> list = taskDao.queryReminderTask();
        if (list.isEmpty()) {
            logger.debug("没有获取到催办任务!");
            return;
        }
        for (ProcessTask task : list) {
            try{
                // 取得流程定义ID
                String procDefId = task.getProcessDefinitionId();
                // 取得节点ID
                String nodeId = task.getTaskDefinitionKey();
                // 获取提醒对象
                List<TaskReminder> taskReminders = taskReminderDao.queryByProcDefAndNodeId(procDefId, nodeId);
                Map<String,Object> vars = runtimeService.getVariables(task.getProcessInstanceId());
                for(TaskReminder taskReminder:taskReminders){
                    String conditionExp=taskReminder.getCondExp();
                    //条件表达式不为空
                    if(StringUtil.isNotEmpty(conditionExp)){
                        boolean  result= groovyScriptEngine.executeBoolean(conditionExp, vars);
                        if(!result){
                            logger.debug("Skip TaskReminder :"+taskReminder.getTaskDueId()+","+taskReminder.getName());
                            continue;
                        }
                    }
                    logger.debug("Execute TaskReminder :"+taskReminder.getTaskDueId()+","+taskReminder.getName());
                    // 获取用户数据
                    Set<String> userSet = getUserByTask(task);
                    //提醒次数如果为0则表示不发送消息
                    if(taskReminder.getTimes()>0){
                        // 处理提醒
                        handReminder(task, taskReminder, userSet, vars);
                    }
                    // 到期处理
                    handlerDueTask(task, taskReminder, userSet);
                }
            }catch (Exception e){
                e.printStackTrace();
                logger.error("执行催办任务出错:" + e.getMessage());
            }
        }

    }

    /**
     * 取得任务的人员数据。
     * @param task
     * @return
     */
    private Set<String> getUserByTask(ProcessTask task) {
        TaskUserService taskUserService = (TaskUserService) AppUtil.getBean(TaskUserService.class);
        Set<String> set = new HashSet<>();
        String assignee = task.getAssignee();
        if (StringUtil.isNotEmpty(assignee)) {
            set.add(assignee);
        }
        else {
            Set<SysUser> users = taskUserService.getCandidateUsers(task.getId());
            for(SysUser user:users){
                set.add(user.getUserId());
            }
        }
        return set;
    }

    /**
     * 处理消息报警
     *
     * @param task
     * @param taskReminder
     * @param userSet
     * @param vars
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public void handReminder(ProcessTask task, TaskReminder taskReminder, Set<String> userSet, Map<String, Object> vars) throws Exception {
        ReminderStateDao reminderStateDao = (ReminderStateDao) AppUtil.getBean(ReminderStateDao.class);
        String taskId = task.getId();
        // 需要提醒的次数。
        int needRemindTimes = taskReminder.getTimes();
        // 离任务起始时间的分钟数
        int reminderStart = taskReminder.getReminderStart();
        // 任务距离触发时间的分钟数
        int interval = taskReminder.getReminderEnd();
        String procDefId = task.getProcessDefinitionId();
        String procInstanceId = task.getProcessInstanceId();
        // 催办任务开始时间
        Date startTime = getRelativeStartTime(procInstanceId, taskReminder.getRelativeNodeId(), taskReminder.getRelativeNodeType());
        //未获取到相对节点的创建或者完成时间
        if(startTime==null){
            return;
        }
        for (String userId : userSet) {
            // 已催办的次数
            int reminderTimes = reminderStateDao.getAmountByUserTaskId(taskId, userId, REMIND);
            // 已达到指定的催办次数。
            if (reminderTimes >= needRemindTimes){
                continue;
            }
            // 计算距离开始时间。
            int start = reminderStart + interval * reminderTimes;
            Date dueDate = null;
            //日历日
            if(new Integer(1).equals(taskReminder.getRelativeTimeType())){
                dueDate = new Date(TimeUtil.getNextTime(TimeUtil.MINUTE, start, startTime.getTime()));
            }
            Date curDate = new Date();
            // 催办时间未到
            if (dueDate.compareTo(curDate) > 0){
                continue;
            }
            Date completeDate = null;
            //日历日
            if(new Integer(1).equals(taskReminder.getRelativeTimeType())){
                completeDate = new Date(TimeUtil.getNextTime(TimeUtil.MINUTE, taskReminder.getCompleteTime(), startTime.getTime()));
            }
            if (completeDate.compareTo(curDate) < 0){
                continue;
            }
            // 该节点的剩余时间
            String completeTime = TimeUtil.getTimeDiff(curDate,completeDate);
            //发送催办消息
            sendMsg(userId, taskReminder, task, completeTime,vars);
            // 记录提醒次数
            saveReminderState(taskId, procDefId, procInstanceId, userId, REMIND);
        }
    }


    private void sendMsg(String userId, TaskReminder taskReminder, ProcessTask task, String time, Map<String, Object> vars) {
        MessageProducer messageProducer = AppUtil.getBean(MessageProducer.class);
        ProcessRunService processRunService =  AppUtil.getBean(ProcessRunService.class);
        SysUserDao sysUserDao =  AppUtil.getBean(SysUserDao.class);
        SysUser sysUser = sysUserDao.getById(userId);
        String msgTemplate = taskReminder.getMsgContent();
        ProcessRun processRun = processRunService.getByProcInstanceId(task.getProcessInstanceId());
        String subject = processRun.getSubject();
        String url = PropertyUtil.getByAlias("serverUrl")   + "/workflow/task/view/toStart"+"?taskId=" + task.getId();
        if (StringUtil.isNotEmpty(msgTemplate)) {
            MessageModel msgModel = getMsg(sysUser, msgTemplate, subject, url, time,vars);
            if (msgModel != null){
                messageProducer.send(msgModel);
            }
        }

    }

    /**
     * 取得内部消息
     *
     * @param sysUser
     * @param msgContent
     * @param title
     * @param vars
     * @return
     */
    private MessageModel getMsg(SysUser sysUser, String msgContent, String title, String url, String time, Map<String, Object> vars) {
        String jumpUrl = "<a href='" + url + "'>" + title + "</a>";
        msgContent = msgContent.replace("${收件人}", sysUser.getFullName())
                .replace("${发件人}", "系统消息")
                .replace("${跳转地址}", jumpUrl)
                .replace("${剩余时间}", time)
                .replace("${事项名称}", title);

        msgContent=replaceVars(msgContent,vars);
        MessageModel innerMessage=new MessageModel(BpmConst.MESSAGE_TYPE_INNER);
        innerMessage.setSubject(title);
        innerMessage.setContent(msgContent);
        innerMessage.setReceiveUser(sysUser);
        innerMessage.setSendDate(new Date());

        Map messageType = new HashMap();
        messageType.put("流程提醒", MessageSend.MESSAGETYPE_FLOWTASK);
        innerMessage.setVars(messageType);


        SysUser sendUser = (SysUser) ContextUtil.getCurrentUser();
        if(sendUser==null) {
            sendUser = new SysUser();
            sendUser.setUserId(SystemConst.SYSTEMUSERID);
            sendUser.setFullName(SystemConst.SYSTEMUSERNAME);
        }
        innerMessage.setSendUser(sendUser);
        return innerMessage;
    }

    private String replaceVars(String content,Map<String, Object> vars){
        for(Map.Entry<String, Object> entry:vars.entrySet()){
            String hold="${"+entry.getKey()+"}";
            if(BeanUtils.isNotEmpty(entry.getValue())){
                content=content.replace(hold, entry.getValue().toString());
            }
        }
        return content;
    }

    /**
     * 设置提醒状态
     * @param taskId
     * @param procDefId
     * @param procInstanceId
     * @param userId
     * @param remindType
     * @throws Exception
     */
    private void saveReminderState(String taskId, String procDefId, String procInstanceId, String userId, int remindType) throws Exception {
        ReminderStateDao reminderStateDao = (ReminderStateDao) AppUtil.getBean(ReminderStateDao.class);
        ReminderState reminderState = new ReminderState();
        reminderState.setId(UUIDUtils.getUUIDFor32());
        reminderState.setProcInstanceId(procInstanceId);
        reminderState.setUserId(userId);
        reminderState.setProcDefId(procDefId);
        reminderState.setTaskId(taskId);
        reminderState.setRemindType(remindType);
        reminderStateDao.add(reminderState);
    }


    /**
     * 处理任务到期。
     *
     * @param task
     * @param taskReminder
     * @param userSet
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public void handlerDueTask(ProcessTask task, TaskReminder taskReminder, Set<String> userSet) throws Exception {
        ReminderStateDao reminderStateDao = (ReminderStateDao) AppUtil.getBean(ReminderStateDao.class);
        GroovyScriptEngine scriptEngine = (GroovyScriptEngine) AppUtil.getBean(GroovyScriptEngine.class);
        BpmService bpmService = (BpmService) AppUtil.getBean("bpmService");
        ProcessRunService processRunService = (ProcessRunService)AppUtil.getBean(ProcessRunService.class);
        String taskId = task.getId();
        String procDefId = task.getProcessDefinitionId();
        String procInstanceId = task.getProcessInstanceId();
        Date relativeStartTime = getRelativeStartTime(procInstanceId, taskReminder.getRelativeNodeId(), taskReminder.getRelativeNodeType());
        //未获取到相对节点的创建或者完成时间
        if(relativeStartTime==null){
            return;
        }

        //日历日
        if(new Integer(1).equals(taskReminder.getRelativeTimeType())){
            Date dueDate = new Date(TimeUtil.getNextTime(TimeUtil.MINUTE, taskReminder.getCompleteTime(), relativeStartTime.getTime()));
            Date curDate = new Date();
            if (dueDate.compareTo(curDate) <= 0){
                handlerAction(taskReminder,processRunService,task,bpmService,scriptEngine);
                // 记录提醒次数
                saveReminderState(taskId, procDefId, procInstanceId, null, COMPLETE);
            }
            //处理预警
            handleTaskWarning(dueDate,taskReminder.getTaskWarningSetList(),task);
        }
    }


    /**
     * 处理任务到期动作
     * @param taskReminder
     * @param processRunService
     * @param task
     * @param bpmService
     * @param scriptEngine
     * @throws Exception
     */
    private void handlerAction(TaskReminder taskReminder,ProcessRunService processRunService,ProcessTask task,BpmService bpmService,GroovyScriptEngine scriptEngine) throws Exception{
        Integer action = taskReminder.getAction();
        String taskId = task.getId();
        ProcessCmd processCmd = new ProcessCmd();
        processCmd.setTaskId(taskId);
        TaskEntity taskEnt = bpmService.getTask(taskId);
        switch (action) {
            // 执行同意操作
            case 1:
                processCmd.setVoteAgree(new Short("1"));
                processRunService.nextProcess(processCmd);
                logger.debug("对该任务执行同意操作");
                break;
            //执行反对操作
            case 2:
                processCmd.setVoteAgree(new Short("2"));
                processRunService.nextProcess(processCmd);
                logger.debug("对该任务执行反对操作");
                break;
            //执行驳回操作
            case 3:
                processCmd.setVoteAgree(new Short("3"));
                processCmd.setBack(1);
                processRunService.nextProcess(processCmd);
                logger.debug("对该任务执行驳回操作");
                break;
            //执行驳回到发起人操作
            case 4:
                processCmd.setVoteAgree(new Short("3"));
                processCmd.setBack(2);
                processRunService.nextProcess(processCmd);
                logger.debug("对该任务执行驳回到发起人操作");
                break;
            //执行交办操作
			case 5:
				String userId = taskReminder.getAssignerId();
				if (StringUtil.isNotEmpty(taskId) && StringUtil.isNotEmpty(userId)) {
					String userName = taskReminder.getAssignerName();
					String description = "任务到期，系统将任务交办给用户【" + userName +"】来处理。";
                    if(taskEnt!=null){
                        String instanceId = taskEnt.getProcessInstanceId();
                        bpmService.delegate(taskId,instanceId, userId,description);
                        logger.debug("对该任务执行交办操作");
                    }
				}
				else{
					logger.debug("任务到期执行交办操作失败，因为未能获取到接受交办的用户。");
				}
				break;
            // 结束流程
            case 6:
                if(taskEnt!=null){
                    String instanceId = taskEnt.getProcessInstanceId();
                    bpmService.endProcessByInstanceId(instanceId,taskEnt.getTaskDefinitionKey(),"任务到期，结束流程");
                    logger.debug("结束流程");
                }
                break;
            // 执行脚本
            case 7:
                String script = taskReminder.getScript();
                Map vars = new HashMap();
                vars.put("task", task);
                scriptEngine.execute(script, vars);
                logger.debug("执行指定的脚本");
                break;
        }
    }


    /**
     * 获取任务相对开始时间
     * @param procInstanceId
     * @param nodeId
     * @param eventType
     * @return
     */
    private Date getRelativeStartTime(String procInstanceId,String nodeId,Integer eventType){
        BpmService bpmService = (BpmService) AppUtil.getBean(BpmService.class);
        HistoricTaskInstanceEntity historicTaskInstanceEntity = bpmService.getHistoricTaskInstanceEntity(procInstanceId, nodeId);
        if(historicTaskInstanceEntity!=null){
            if(new Integer(0).equals(eventType)){
                return historicTaskInstanceEntity.getCreateTime();
            }else{
                return historicTaskInstanceEntity.getEndTime();
            }
        }
        HistoricProcessInstanceEntity historicProcessInstanceEntity = bpmService.getHistoryInstanceByInstanceId(procInstanceId);
        if(historicProcessInstanceEntity!=null){
            return historicProcessInstanceEntity.getStartTime();
        }
        return null;
    }

    /**
     * 处理任务预警
     * @param dueDate
     * @param taskWarningSetList
     * @param task
     */
    private void handleTaskWarning(Date dueDate, List<TaskWarningSet> taskWarningSetList, ProcessTask task) {
        if(BeanUtils.isEmpty(taskWarningSetList)){
            return;
        }

        //当前需要预警项
        TaskWarningSet currentWarning = null;
        //最后预警的那个时间
        Date lastTime  = null;
        Date curData  = new Date();
        for(TaskWarningSet warning : taskWarningSetList){
            int interval = 0;
            Date currentWaringTime = null;

            if(TaskWarningSet.RELATIVE_TYPE_BEFOR.equals(warning.getRelativeType())){
                interval = -(warning.getRelativeDueTime());
            }else{
                interval = warning.getRelativeDueTime();
            }
            currentWaringTime = new Date(TimeUtil.getNextTime(TimeUtil.MINUTE,interval,dueDate.getTime()));
            //如果到了预警日期,并且晚于上条预警项时间,设置最晚预警项
            if(curData.after(currentWaringTime)){
                if(lastTime == null || lastTime.before(currentWaringTime)){
                    lastTime = currentWaringTime;
                    currentWarning = warning;
                }
            }
        }
        if(currentWarning != null && task.getPriority() != currentWarning.getLevel()){
            TaskDao taskDao = (TaskDao) AppUtil.getBean(TaskDao.class);
            taskDao.updateTaskPriority(task.getId(),currentWarning.getLevel());
        }
    }

}
